package com.ren.scale.service.impl;

import com.alibaba.fastjson.JSON;
import com.ren.feign.api.recl_user.ReclUserClient;
import com.ren.scale.constant.CachePrefix;
import com.ren.scale.constant.MQConstant;
import com.ren.scale.entity.ReclOrderErrorLog;
import com.ren.scale.entity.ReclScale;
import com.ren.scale.entity.ReclScaleOrder;
import com.ren.scale.mapper.ReclScaleOrderMapper;
import com.ren.scale.service.ReclOrderErrorLogService;
import com.ren.scale.service.ReclScaleOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ren.scale.service.ReclScaleService;
import com.ren.utils.snowflake.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author RZY
 * @since 2022-07-18
 */
@Service
@Slf4j
public class ReclScaleOrderServiceImpl extends ServiceImpl<ReclScaleOrderMapper, ReclScaleOrder> implements ReclScaleOrderService {

    @Autowired
    ReclScaleService scaleService;

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @Autowired
    ThreadPoolTaskExecutor threadPool;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    ReclOrderErrorLogService orderErrorLogService;

    @Override
    public ReclScaleOrder getFrontScaleOrder(Long userId, Long scaleId) {
        //获取量表信息
        ReclScale reclScale = scaleService.getScaleOfOrder(scaleId);
        //封装订单对象ReclScaleOrder
        ReclScaleOrder scaleOrder = new ReclScaleOrder();

        //生成订单确认页的唯一token令牌，防止订单重复提交
        String orderToken = UUID.randomUUID().toString().replace("-", "");
        //令牌放入redis
        String key = String.format("%s::%s", CachePrefix.USER_ORDER_TOKEN_PREFIX, userId);
        redisTemplate.opsForValue().set(key, orderToken, 30L, TimeUnit.MINUTES);
        //令牌一起封装
        scaleOrder.setScaleId(scaleId)
                .setUserId(userId)
                .setTitle(reclScale.getTitle())
                .setScaleCover(reclScale.getScaleCover())
                .setDescription(reclScale.getDescription())
                .setOrderToken(orderToken)
                .setTotalFee(reclScale.getScalePrice());
        return scaleOrder;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean submitScaleOrder(ReclScaleOrder scaleOrder) {
        //1.验证下单令牌【令牌对比和和删除保证原子性】
        String orderToken = scaleOrder.getOrderToken();
        //保证原子操作的校验LUA脚本
        String LuaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        //redis中令牌key
        String key = String.format("%s::%s", CachePrefix.USER_ORDER_TOKEN_PREFIX, scaleOrder.getUserId());
        //执行LUA脚本(原子验证并删除令牌)
        Long result = redisTemplate.execute(new DefaultRedisScript<>(LuaScript, Long.class),
                Collections.singletonList(key), orderToken);
        //验证失败
        if(result == null || result == 0L) return false;
        //验证成功后的操作
        IdWorker idWorker = new IdWorker(0, 0);
        //生成唯一orderNo(雪花算法)
        Long orderNo = idWorker.nextId();
        scaleOrder.setOrderNo(orderNo);
        log.info("orderNo: {}", orderNo);
        //保存订单信息
        boolean res = this.save(scaleOrder);
        if(res) {
            //线程异步提交订单任务
            threadPool.submit(() -> {
                //异步提交MQ订单发送任务
                sendScaleOrderToMQ(orderNo);
                log.info("线程池执行订单提交任务成功:" + scaleOrder);
            });
            return true;
        }
        return false;
    }

    private void sendScaleOrderToMQ(Long orderNo) {
        //1.准备CorrelationData
        CorrelationData correlationData = new CorrelationData();
        //设置唯一消息id(orderNo)
        correlationData.setId(orderNo.toString());
        //准备持久消息
        Message message = MessageBuilder.withBody(orderNo.toString().getBytes(StandardCharsets.UTF_8))
                .setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();
        //2.准备ConfirmCallback回调
        correlationData.getFuture().addCallback(res -> {
            //投递结果
            if(res == null || res.isAck()) {
                //ACK
                log.info("消息投递交换机成功: {}", correlationData);
            } else {
                //NACK
                log.info("消息投递交换机失败: {}", correlationData);
                //进行消息重发
                rabbitTemplate.convertAndSend(MQConstant.ORDER_EXCHANGE, MQConstant.ORDER_DELAY_ROUTING_KEY, message, correlationData);
            }
        }, exception -> {
            //异常回调
            log.info("消息投递交换机失败,异常原因 : {}", exception.getMessage());
            ReclOrderErrorLog orderErrorLog = new ReclOrderErrorLog();
            //封装订单错误日志信息，方便后续进行消息补偿
            orderErrorLog.setOrderNo(Long.valueOf(Objects.requireNonNull(correlationData.getId())))
                    .setErrorMessage(exception.getMessage());
            boolean res = orderErrorLogService.save(orderErrorLog);
            if(res) log.info("订单错误日志信息记录成功: {}", orderErrorLog);
        });

        rabbitTemplate.convertAndSend(MQConstant.ORDER_EXCHANGE, MQConstant.ORDER_DELAY_ROUTING_KEY, message, correlationData);
    }

}
